// http://davenport.sourceforge.net/ntlm.html

//TODO: guard

#include <string>

//#include <boost/algorithm/string.hpp>
#include <boost/algorithm/string/case_conv.hpp>

#include <boost/config.hpp>
#include <boost/cstdint.hpp>

#include <boost/io/detail/bin_manip.hpp>


#include <boost/cstdint.hpp>
//#include <boost/endian/integers.hpp>
#include <boost/endian/conversion.hpp>
#include <boost/endian/conversion2.hpp>


#include "os_version.hpp"
#include "os_helper.hpp"

#ifdef BOOST_WINDOWS
#include <windows.h>
#else
#error Not defined yet for OS != Windows
#endif


namespace ntlm
{
	
namespace message_type
{
	enum message_type
	{
		type1 = 1,
		type2 = 2,
		type3 = 3
	};
} //namespace message_type

namespace flags
{
	enum flags
	{
		negotiate_unicode = 0x00000001,
		negotiate_oem = 0x00000002,
		request_target = 0x00000004,
		negotiate_sign = 0x00000010,
		negotiate_seal = 0x00000020,
		negotiate_datagram_style = 0x00000040,
		negotiate_lan_manager_key = 0x00000080,
		negotiate_netware = 0x00000100,
		negotiate_ntlm = 0x00000200,
		negotiate_anonymous = 0x00000800,
		negotiate_domain_supplied = 0x00001000,
		negotiate_workstation_supplied = 0x00002000,
		negotiate_local_call = 0x00004000,
		negotiate_always_sign = 0x00008000,
		target_type_domain = 0x00010000,
		target_type_server = 0x00020000,
		target_type_share = 0x00040000,
		negotiate_ntlm2_key = 0x00080000,
		request_init_response = 0x00100000,
		request_accept_response = 0x00200000,
		request_non_nt_session_key = 0x00400000,
		negotiate_target_info = 0x00800000,
		negotiate_128 = 0x20000000,
		negotiate_key_exchange = 0x40000000,
		negotiate_56 = 0x80000000
	};
} //namespace flags

//struct security_buffer
//{
//	boost::uint16_t length;
//	boost::uint16_t allocated_space;
//	boost::uint32_t offset;
//};







struct type1;
struct type2;
struct type3;


template <typename MessageType>
struct message_type_traits;

template <>
struct message_type_traits<type1>
{
	enum {value = 1};
};
template <>
struct message_type_traits<type2>
{
	enum {value = 2};
};
template <>
struct message_type_traits<type3>
{
	enum {value = 3};
};


struct security_buffer
{
	boost::uint16_t length;
	boost::uint16_t allocated_space;
	boost::uint32_t offset;
};

std::ostream& operator<<( std::ostream & ostr, security_buffer const& sb )
{
	using boost::endian2::little;
	using boost::bin;

	ostr << bin( little( sb.length ) );
	ostr << bin( little( sb.allocated_space ) );
	ostr << bin( little( sb.offset ) );

	return ostr;
}

std::istream& operator>>( std::istream & istr, security_buffer & sb )
{
	//using boost::endian2::little;
	using boost::bin;
	using boost::uint32_t;
	using boost::uint16_t;
	using boost::endian::little_to_native;

	uint16_t temp16;
	uint32_t temp32;


	istr >> bin( temp16 );
	little_to_native(temp16, sb.length);

	istr >> bin( temp16 );
	little_to_native(temp16, sb.allocated_space);

	istr >> bin( temp32 );
	little_to_native( temp32, sb.offset );

	return istr;
}

security_buffer make_security_buffer( std::string const & str, boost::uint32_t offset )
{
	security_buffer sb;
	sb.length = str.length();
	sb.allocated_space = str.length();
	sb.offset = offset;

	return sb;
}




struct target_information
{
	boost::uint16_t type;
	boost::uint16_t length;
	std::wstring content;
};


std::istream& operator>>( std::istream & istr, target_information & ti )
{
	using boost::endian2::little;
	using boost::bin;
	using boost::endian::little_to_native;

	boost::uint16_t	temp16;

	istr >> bin( temp16 );
	little_to_native( temp16, ti.type );

	istr >> bin( temp16 );
	little_to_native( temp16, ti.length );

	//TODO: leer ti.content

	return istr;
}




//void write_security_buffer( std::ostream & ostr, std::string const & str, boost::uint32_t offset )
//{
//	using boost::endian2::little;
//	using boost::bin;
//	using boost::uint32_t;
//	using boost::uint16_t;
//
//	uint16_t size = str.length();
//
//	ostr << bin( little( size ) );
//	ostr << bin( little( size ) );
//	ostr << bin( little( offset ) );
//}
//
//void write_os_version( std::ostream & ostr, os_version const& osv )
//{
//	using boost::endian2::little;
//	using boost::bin;
//
//	ostr << osv.major;
//	ostr << osv.minor;
//	ostr << bin( little( osv.build_number ) );
//	ostr << bin( little( osv.unknown ) );
//}



std::ostream& operator<<( std::ostream & ostr, os_version const& osv )
{
	using boost::endian2::little;
	using boost::bin;

	ostr << osv.major;
	ostr << osv.minor;
	ostr << bin( little( osv.build_number ) );
	ostr << bin( little( osv.unknown ) );

	return ostr;
}

std::istream& operator>>( std::istream & istr, os_version & osv )
{
	using boost::endian2::little;
	using boost::bin;
	using boost::endian::little_to_native;

	boost::uint16_t	temp16;
	boost::uint32_t temp32;

	istr >> osv.major;
	istr >> osv.minor;

	istr >> bin( temp16 );
	little_to_native( temp16, osv.build_number );

	istr >> bin( temp32 );			//unknown, skip data

	return istr;
}







template <typename MessageType>
class message
{
private:
	static const std::size_t header_size = 16;
	
	//static const std::size_t security_buffer_size = 8;
	//static const std::size_t security_buffer_size = sizeof(security_buffer);


	
public:
	void flags( int flags )
	{
		flags_ = flags;
	}

	void supplied_domain( std::string const & supplied_domain )
	{
		supplied_domain_ = supplied_domain;
	}

	void supplied_workstation( std::string const & supplied_workstation )
	{
		supplied_workstation_ = supplied_workstation;
	}

	void set_os_version( unsigned char major, unsigned char minor, boost::uint_least16_t build_number )
	{
		//detail::init_os_version(os_version_);
		os_version_.major = major;
		os_version_.minor = minor;
		os_version_.build_number = build_number;
	}

	void set_os_version( os_version const & osv )
	{
		os_version_.major = osv.major;
		os_version_.minor = osv.minor;
		os_version_.build_number = osv.build_number;
	}

	friend std::ostream& operator<<( std::ostream & ostr, message gm )
	{
		using boost::endian2::little;
		using boost::bin;
		using boost::uint32_t;

		uint32_t type = message_type_traits<MessageType>::value;

		//header
		ostr << "NTLMSSP" << '\0';				// 8 bytes
		ostr << bin( little( type ) );			// 4 bytes
		ostr << bin( little( gm.flags_ ) );		// 4 bytes
		//end header

		std::size_t pos = header_size + (sizeof(security_buffer) * 2) + os_version::size;



		////if ( gm.supplied_workstation_.size() != 0 )
		////{
		//	//write_security_buffer( ostr, gm.supplied_workstation_, pos + gm.supplied_domain_.size() );  //16 bytes
		//	ostr << make_security_buffer( gm.supplied_workstation_, pos + gm.supplied_domain_.size() );  //16 bytes
		////}

		//if ( gm.supplied_workstation_.size() != 0 )
		//{
			//write_security_buffer( ostr, gm.supplied_workstation_, pos );  //16 bytes
			ostr << make_security_buffer( gm.supplied_workstation_, pos );  //16 bytes
		//}


		//if ( gm.supplied_domain_.size() != 0 )
		//{
			//write_security_buffer( ostr, gm.supplied_domain_, pos + gm.supplied_workstation_.size() );
			ostr << make_security_buffer( gm.supplied_domain_, pos + gm.supplied_workstation_.size() );
		//}


		//write_os_version( ostr, gm.os_version_ );										// 8 bytes
		ostr << gm.os_version_;

		//if ( gm.supplied_domain_.size() != 0 )
		//{
			ostr << gm.supplied_domain_;
		//}

		//if ( gm.supplied_workstation_.size() != 0 )
		//{
			ostr << gm.supplied_workstation_;
		//}

		return ostr;
	}


private:
	boost::uint32_t flags_;
	std::string supplied_domain_;				//optional
	std::string supplied_workstation_;			//optional
	os_version os_version_;						//optional
};





template <typename MessageType>
const message<MessageType> make_from_system()
{
	message<MessageType> gm;

	//TODO: pasar a una funcion Default_Flags o algo asi...
	//gm.flags( flags::negotiate_unicode | flags::request_target | flags::negotiate_ntlm | flags::negotiate_always_sign );
	gm.flags(	flags::negotiate_unicode 
				| flags::negotiate_oem 
				| flags::request_target 
				| flags::negotiate_ntlm 
				| flags::negotiate_domain_supplied 
				| flags::negotiate_workstation_supplied );
	
	gm.supplied_domain( boost::to_upper_copy( detail::get_user_domain_name() ) );
	gm.supplied_workstation( boost::to_upper_copy( detail::get_workstation_name() ) );
	gm.set_os_version( detail::get_system_os_version() );

	return gm;
}

} //namespace ntlm





////NTLMSSP signature:			"NTLMSSP\0" (hexadecimal "0x4e544c4d53535000"). 
////message type:					Next is a long containing the message type (1, 2, or 3). A Type 1 message, for example, has type "0x01000000" in hex. 
//struct header
//{
//	char signature[8];		//unsigned char
//	boost::endian::ulittle32_t message_type;
//};
//
//struct message
//{
//	header header;
//	boost::endian::ulittle32_t flags;
//};



////header make_header( boost::endian::ulittle32_t type )
//header make_header( message_type::message_type type )
//{
//	header h;
//
//	//h.signature = "NTLMSSP\0";
//	strncpy(h.signature, "NTLMSSP\0", 8);	//TODO: strncpy is unsafe
//
//	h.message_type = type;
//
//	return h;
//}
//
//
//message make_message( message_type::message_type type )
//{
//	message m;
//	m.header = make_header( type );
//	m.flags = 0;
//
//	return m;
//}
//
//void add_flag( message & m, flags::flags f )
//{
//	m.flags += f;
//}
